#!/usr/bin/env python
#
# Copyright 2011 Justin Bruce Van Horne <justinvh@gmail.com>
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#  http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

""" Handles the animations of characters.

 CharacterAnimation: Defines an animation class for characters.
"""

import json
import pyglet

from pyglet.gl import *
from pyglet.image import ImageGrid, TextureGrid, AnimationFrame, Animation
from pyglet.sprite import Sprite

IDLE = 'idle'
FORWARD = 'move_north'
DEFAULT = FORWARD


_animations = {}


def _error(what):
    import sys
    print what
    sys.exit()


class CharacterAnimation(Animation):
    """Describes a character animation."""
    def __init__(self, frames, config):
        self._reverse = config.get('reverse_on_end', False)
        super(CharacterAnimation, self).__init__(frames)


class Character(Sprite):
    """Creates an instance of an animated character in the game.
    The animations are controlled via a play() routine. Multiple
    animations can be tied to a character so they can switch through
    them without the need for a new character."""
    _reverse = False
    _animation_name = None
    _vertex_lists = dict()
    _groups = dict()

    def __init__(self, animations, batch=None, auto=False):
        """Create a new character."""
        self._animations = animations
        default = self._animations[DEFAULT]
        super(Character, self).__init__(img=default, batch=batch)
        self.auto = auto
        map(self._add_animation, self._animations)
        self.switch_animation(DEFAULT)
        self._update_position()
        self._update_color()

    def _add_animation(self, anim):
        """Adds a new animastion to the animation list.
        Creates the vertex list for the specified animation
        and adds it to the respective group.
        Animations can then be switched via `switch_animation`"""
        img = self._animations[anim]
        texture = img.frames[0].image.get_texture()
        self._groups[anim] = pyglet.sprite.SpriteGroup(
            texture, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, None)
        if self._batch is None:
            self._vertex_lists[anim] = graphics.vertex_list(4,
                'v2i/%s' % self._usage,
                'c4B', ('t3f', texture.tex_coords))
        else:
            self._vertex_lists[anim] = self._batch.add(4,
                GL_QUADS, self._groups[anim],
                'v2i/%s' % self._usage,
                'c4B', ('t3f', texture.tex_coords))

    def switch_animation(self, animation_name):
        """Switches to a new animation. The previous vertex list is
        'removed' for the time being."""
        if animation_name == self._animation_name:
            return
        self._frame_index = 0
        self._animation_name = animation_name
        self._reverse = False
        self._animation = self._animations[animation_name]
        self._vertex_list.vertices[:] = [0, 0, 0, 0, 0, 0, 0, 0]
        self._vertex_list = self._vertex_lists[animation_name]

    def play(self):
        """Play a frame into the animation."""
        self._animate(0, True)

    def reset(self):
        """Resets the animation."""
        self._frame_index = 0
        self._animate(0, True)

    def frame(self, frame_index):
        """Forces the current frame to a specified frame."""
        self._frame_index = frame_index - 1

    def _animate(self, dt, force=False):
        """Animates a frame. This overrides the inherited
        animation function for the sprite class. There is a lot
        of work left to do in the logic."""
        if not self.auto and not force:
            return

        if self._reverse:
            self._frame_index -= 1
        else:
            self._frame_index += 1

        if self._frame_index >= len(self._animation.frames):
            if self._animation._reverse:
                self._reverse = True
                self._frame_index -= 1
            else:
                self._frame_index = 0
                self.dispatch_event('on_animation_end')
            # Deleted in event handler.
            if self._vertex_list is None:
                return
        elif self._frame_index == 0:
            self._reverse = False

        frame = self._animation.frames[self._frame_index]
        self._set_texture(frame.image.get_texture())

        if frame.duration is not None and self.auto:
            duration = frame.duration - (self._next_dt - dt)
            duration = min(max(0, duration), frame.duration)
            clock.schedule_once(self._animate, duration)
            self._next_dt = duration
        elif frame.duration is None and self.auto:
            self.dispatch_event('on_animation_end')


def parse_animations(what):
    """Loads an animation from a file specified as JSON"""
    try:
        print "Parsing %s..." % what
        material = open("game/animations/%s" % what)
        material_json = json.loads(material.read())
        _animations[what] = {}
        for index in xrange(len(material_json)):
            definition = material_json[index]
            animation_def = definition.keys()[0]
            definition = definition[animation_def]
            _animations[what][animation_def] = {}
            frames = []
            sprite = "game/%s" % definition["sprite"]
            rows, cols = definition["size"]
            time = definition["time"]
            print "%s: %s: Expecting %d rows and %d cols sprite table" % (
            what, animation_def, rows, cols)
            animation_sprite = pyglet.image.load(sprite)
            animation_seq = ImageGrid(animation_sprite, rows, cols)
            animation_tex_seq = TextureGrid(animation_seq)
            animation_frames = []
            for j, frame in enumerate(animation_tex_seq):
                animation_frame = AnimationFrame(frame, time[j])
                animation_frames.append(animation_frame)
                _animations[what][animation_def] = CharacterAnimation(
                        animation_frames, definition)
        return _animations[what], len(material_json)
    except IOError:
        _error("Failed to open material/%s.json " +
                "or sprite/%s.png for parsing." % (what, what))
    except ValueError, e:
        _error("Failed to parse material/%s.json: %s" % (what, e))
